#pragma once

#include <vector>
#include <cmath>
#include <cassert>
#include <thread>

class Wavelet {
   public:
    virtual void decomposite(std::vector<float> &data, int w, int h) = 0;
    virtual void reconstruct(std::vector<float> &data, int w, int h) = 0;
};

class Haar : public Wavelet {
   public:
    Haar() {
        gfilter[0] = 1 / sqrt(2.0);
        gfilter[1] = 1 / sqrt(2.0);
        hfilter[0] = 1 / sqrt(2.0);
        hfilter[1] = -1 / sqrt(2.0);
    }
    virtual void decomposite(std::vector<float> &data, int w, int h) {
        assert(data.size() == w * h);
        std::vector<float> tmp;
        tmp.resize(data.size());
        // rows
        for (int row = 0; row < h; row++) {
            int k = 1;
            while (k * 2 <= w) {
                k = k * 2;
            }
            while (1 < k) {
                k = k / 2;
                for (int col = 0; col < k; col++) {
                    tmp[row * w + col] = gfilter[0] * data[row * w + col * 2] + gfilter[1] * data[row * w + col * 2 + 1];
                    tmp[row * w + col + k] = hfilter[0] * data[row * w + col * 2] + hfilter[1] * data[row * w + col * 2 + 1];
                }
                for (int col = 0; col < 2 * k; col++) {
                    data[row * w + col] = tmp[row * w + col];
                }
            }
        }
        // columns
        for (int col = 0; col < w; col++) {
            int k = 1;
            while (k * 2 <= h) {
                k = k * 2;
            }
            while (1 < k) {
                k = k / 2;
                for (int row = 0; row < k; row++) {
                    tmp[row * w + col] = gfilter[0] * data[(row * 2) * w + col] + gfilter[1] * data[(row * 2 + 1) * w + col];
                    tmp[(row + k) * w + col] = hfilter[0] * data[(row * 2) * w + col] + hfilter[1] * data[(row * 2 + 1) * w + col];
                }
                for (int row = 0; row < 2 * k; row++) {
                    data[row * w + col] = tmp[row * w + col];
                }
            }
        }
    }
    virtual void reconstruct(std::vector<float> &data, int w, int h) {}

   private:
    float gfilter[2], hfilter[2];
};